/**
 * Copyright 1998-2006, CHISEL Group, University of Victoria, Victoria, BC, Canada.
 * All rights reserved.
 */
package ca.uvic.cs.cogz.mapping.ontology;

import java.awt.Dimension;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;

import javax.swing.JFrame;
import javax.swing.JOptionPane;

import org.eclipse.eodm.owl.transformer.EODMOWLTransformerException;
import org.eclipse.eodm.owl.transformer.Ecore2OWL;

import ca.uvic.cs.cogz.PromptUIPlugin;
import ca.uvic.cs.cogz.mapping.SuggestionsManager;
import ca.uvic.cs.cogz.mapping.TemporaryMappingManager;
import ca.uvic.cs.cogz.mapping.core.MappingCollection;
import ca.uvic.cs.cogz.mapping.core.MappingUtil;
import ca.uvic.cs.cogz.mapping.core.MappingTypeManager.MappingType;
import ca.uvic.cs.cogz.mapping.filter.TreeFilterManager;
import ca.uvic.cs.cogz.mapping.ui.MappingVisualizationPanel;
import ca.uvic.cs.cogz.mapping.ui.progress.ProgressDialog;
import ca.uvic.cs.cogz.mapping.ui.progress.ProgressMonitor;
import ca.uvic.cs.cogz.stats.StatsManager;
import ca.uvic.cs.cogz.util.TreeUtil;
import edu.stanford.smi.protege.model.Cls;
import edu.stanford.smi.protege.model.Frame;
import edu.stanford.smi.protege.model.Instance;
import edu.stanford.smi.protege.model.KnowledgeBase;
import edu.stanford.smi.protege.model.Project;
import edu.stanford.smi.protege.model.Slot;
import edu.stanford.smi.protege.ui.ProjectManager;
import edu.stanford.smi.protege.util.CollectionUtilities;
import edu.stanford.smi.protege.util.URIUtilities;
import edu.stanford.smi.protegex.owl.jena.CreateOWLProjectFromFilePlugin;
import edu.stanford.smi.protegex.owl.jena.JenaKnowledgeBaseFactory;
import edu.stanford.smi.protegex.prompt.Mappings;
import edu.stanford.smi.protegex.prompt.Preferences;
import edu.stanford.smi.protegex.prompt.ProjectsAndKnowledgeBases;
import edu.stanford.smi.protegex.prompt.PromptTab;
import edu.stanford.smi.protegex.prompt.event.PromptAdapter;
import edu.stanford.smi.protegex.prompt.event.PromptEvent;
import edu.stanford.smi.protegex.prompt.event.PromptListenerManager;
import edu.stanford.smi.protegex.prompt.explanation.Explanation;
import edu.stanford.smi.protegex.prompt.operation.MergeClsesOperation;
import edu.stanford.smi.protegex.prompt.operation.MergeSlotsOperation;
import edu.stanford.smi.protegex.prompt.operation.Operation;
import edu.stanford.smi.protegex.prompt.plugin.PluginManager;
import edu.stanford.smi.protegex.prompt.plugin.model.MappingStoragePlugin;
import edu.stanford.smi.protegex.prompt.plugin.util.MappingStoragePluginOrder;
import edu.stanford.smi.protegex.prompt.ui.ActionListPane;
import edu.stanford.smi.protegex.prompt.ui.SuggestionListPane;
import edu.stanford.smi.protegex.prompt.ui.TabComponent;

/**
 * Facade for handling loading/parsing of ontologies.
 * 
 * @author Sean Falconer
 * @date 10-Jan-07
 */
public class DataLoaderFacade {
	/** static reference for singleton design pattern */
	private static DataLoaderFacade ontologyFacade;

	/** current source, target, and mapping knowledge bases being used in the tool */
	private KnowledgeBase sourceKb;
	private KnowledgeBase targetKb;
	private KnowledgeBase mappingKb;

	/** manager classes for temporary mappings, suggestions, and all mappings */
	private TemporaryMappingManager temporaryMappingManager;
	private SuggestionsManager suggestionsManager;
	private MappingCollection mappingCollection;

	private MappingVisualizationPanel mappingVisualizationPanel;
	
	private JFrame mappingFrame;
	private JFrame standaloneInputFrame;
	
	private boolean showSlots;
	private boolean standaloneMode;

	private DataLoaderFacade() {
		mappingCollection = new MappingCollection();
	}

	public static DataLoaderFacade getInstance() {
		if (ontologyFacade == null) {
			ontologyFacade = new DataLoaderFacade();
		}

		return ontologyFacade;
	}
	
	public JFrame getStandaloneInputFrame() {
		return standaloneInputFrame;
	}
	
	public boolean isStandaloneMode() {
		return standaloneMode;
	}

	public void refreshMapping() {
		if (mappingVisualizationPanel != null && mappingVisualizationPanel.isVisible()) {
			mappingCollection.reset();

			loadMapping();
			loadTemporaryMappings();
			loadSuggestedMappings();
			mappingVisualizationPanel.refresh();
		}
	}

	/**
	 * Loads the mapping frame with the given source, target, and mapping knowledge bases.
	 * 
	 * @param sourceKb The source project knowledge base.
	 * @param targetKb The target project knowledge base.
	 * @param mappingKb The mapping project knowledge base.
	 * @param temporaryMappings List of temporary mappings to display.
	 */
	public MappingVisualizationPanel loadMapping(KnowledgeBase sourceKb, KnowledgeBase targetKb, KnowledgeBase mappingKb, TemporaryMappingManager temporaryMappingManager,
			SuggestionsManager suggestionsManager, StatsManager sourceStatsManager, StatsManager targetStatsManager) {
		this.sourceKb = sourceKb;
		this.targetKb = targetKb;
		this.mappingKb = mappingKb;
		this.temporaryMappingManager = temporaryMappingManager;
		this.suggestionsManager = suggestionsManager;
		this.mappingCollection.reset();
		TreeUtil.resetCachedObjects();
		TreeFilterManager.getInstance().reset();

		loadMapping();
		loadSuggestedMappings();
		loadTemporaryMappings();

		if (mappingVisualizationPanel == null) {
			initPromptListener();
		}

		JFrame mappingFrame = PromptTab.getMainWindow();
		if(mappingFrame == null) {
			mappingFrame = this.mappingFrame;
		}
		
		this.mappingVisualizationPanel = new MappingVisualizationPanel(mappingFrame, sourceStatsManager, targetStatsManager);

		return mappingVisualizationPanel;
	}

	private void initPromptListener() {
		PromptListenerManager.addListener(new PromptAdapter() {
			public void operationStarted(Operation operation, PromptEvent event) {
				operationPerformed(operation);
				
				//if (operation instanceof MergeClsesOperation || operation instanceof MergeSlotsOperation) {
				//	TransformationEngine.getInstance().createTransformation(operation);
				//}
			}

			public void operationCompleted(Operation operation, PromptEvent event) {
				if (operation instanceof MergeClsesOperation || operation instanceof MergeSlotsOperation) {
					DataLoaderFacade.getInstance().refreshMapping();
				}
			}
		});
	}

	/**
	 * Called when running CogZ in standalone mode.  The sourceProject and targetProject are the two models being mapped.  These
	 * projects are loaded (possibly converted to pprj files) and the source is set as the current Protege project.  Prompt's merge 
	 * algorithm is called and after running, Prompt calls CogZ's PromptUIPlugin class.
	 */
	public void loadMapping(JFrame frame, final String sourceProject, final String targetProject) {
		final ProgressMonitor progressMonitor = new ProgressMonitor();
		
		standaloneInputFrame = frame;
		standaloneMode = true;

		// check to make sure we haven't already added this listener
		if(mappingFrame == null) {
			// add a component listener to the ProgessDialog so that we can display the main GUI
			// after the loading of the ontologies is complete
			ProgressDialog.getInstance().addComponentListener(new ComponentAdapter() {
				public void componentHidden(ComponentEvent e) {
					if (!progressMonitor.isCancelled()) {
						// hide the file opening dialog
						standaloneInputFrame.setVisible(false);
	
						// setup the mapping frame
						mappingFrame = new JFrame("CogZ Mapping Tool : " + getProjectName(sourceProject) + " - " + getProjectName(targetProject));
						mappingFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	
						PromptUIPlugin promptUIPlugin = new PromptUIPlugin();
						promptUIPlugin.afterLoad();
						Dimension dim = new Dimension(1200, 800);
						
						mappingFrame.getContentPane().add(promptUIPlugin.createContentPane(dim));
						
						mappingFrame.pack();
						mappingFrame.setLocation(400, 200);
						
						mappingFrame.setPreferredSize(dim);
						mappingFrame.setSize(dim);
						mappingFrame.setVisible(true);
					}
					else {
						standaloneInputFrame.setVisible(true);
					}
				}
			});
		}

		// create a new thread and load the input ontologies as well as the mappings
		new Thread(new Runnable() {
			@SuppressWarnings("unchecked")
			public void run() {
				try {
					ProgressDialog.displayProgressMonitor(progressMonitor, standaloneInputFrame);
					
					progressMonitor.setProgressText("Loading source ontology");
					KnowledgeBase sourceKb = loadOntology(sourceProject);
					KnowledgeBase targetKb = loadOntology(targetProject);
					
					String sourceProject = sourceKb.getProject().getProjectURI().getPath();
					String targetProject = targetKb.getProject().getProjectURI().getPath();					
					ProjectManager.getProjectManager().setCurrentProject(sourceKb.getProject(), false, true);
					
					progressMonitor.setProgressText("Configuring Prompt");
					PluginManager.getInstance().setActiveComparisonPlugin(null);
		        	Preferences.pluginMatch(false);
		        	
		        	LinkedList listOfStorageOptions = PluginManager.getInstance().getPlugins(PluginManager.PLUGIN_MAPPING_STORAGE);
		    		MappingStoragePluginOrder.putSimplePluginFirst (listOfStorageOptions);
		    		
		    		Collection storagePlugins = new ArrayList();
		    		storagePlugins.add(listOfStorageOptions.get(0));
		    		
		    		PluginManager.getInstance().setActiveMappingStoragePlugins(storagePlugins);
					
		    		new PromptTab();
					PromptTab.setMapping();
					PromptTab.setCompareSources(true);
					Preferences.approximateMatch(true);
					
					if(targetProject.charAt(0) == '/') targetProject = targetProject.substring(1);
					if(sourceProject.charAt(0) == '/') sourceProject = sourceProject.substring(1);
					System.out.println("Projects " + targetProject + " " + sourceProject);
					
					String[] projectFiles = new String[] {targetProject, "", "", sourceProject};
					String[] projectNames = new String[] {getProjectName(targetProject), "", "", getProjectName(sourceProject)};
					
					progressMonitor.setProgressText("Comparing ontologies");
					
					PromptListenerManager.addListener(new PromptAdapter() {
						@Override
						public void taskComplete(PromptEvent event, boolean interrupted) {
							if(!interrupted) {
								// have to force the default selection listener to be added
								ActionListPane suggestionListPane = ((SuggestionListPane)TabComponent.getTodoPane()).getTodoList();
								suggestionListPane.addSelectionListener();
								
								progressMonitor.setCompleted(true);
							}
						}
					});
					
					PromptTab.setUpMerging(projectFiles, projectNames, -1, true);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}).start();
	}
	
	private String getProjectName(String projectPath) {
		int start = projectPath.lastIndexOf(File.separatorChar)+1;
		int end = projectPath.lastIndexOf(".");
		
		if(start >= 0 && end >= 0) {
			return projectPath.substring(start, end);
		}
		
		return "";
	}

	/**
	 * When an operation is performed, this method checks for conflicting temporary operations.
	 * 
	 * @param operation The operation being performed by Prompt
	 */
	private void operationPerformed(Operation operation) {
		// we only care about merge/mapping operations
		if (operation instanceof MergeClsesOperation) {
			if (temporaryMappingManager.isTemporaryOperation(operation)) {
				temporaryMappingManager.removeTemporaryMapping(operation);
			}
			if (hasConflictingTempOperation(operation)) {
				int result = JOptionPane.showConfirmDialog(null, "There exists conflicting temporary operations for this mapping.  Remove temporary mappings?", "Conflicts", JOptionPane.YES_NO_OPTION);
				if (result == JOptionPane.YES_OPTION) {
					removeConflictingTemporaries(operation);
				}
			}
		}
	}

	/**
	 * Checks to see if the passed in operation conflicts with an existing temporary operation.
	 * 
	 * @param operation
	 * @return True if there is a conflict.
	 */
	private boolean hasConflictingTempOperation(Operation operation) {
		ArrayList<Operation> tempOperations = temporaryMappingManager.getTemporaryMappings();
		for (Operation tempOperation : tempOperations) {
			if (tempOperation.getArgs().getArg(0).equals(operation.getArgs().getArg(0)) || tempOperation.getArgs().getArg(1).equals(operation.getArgs().getArg(1))) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Removes all temporary operations that conflict with the operation being performed.
	 * 
	 * @param operation
	 */
	private void removeConflictingTemporaries(Operation operation) {
		ArrayList<Operation> tempOperations = temporaryMappingManager.getTemporaryMappings();
		for (int i = 0; i < tempOperations.size(); i++) {
			Operation tempOperation = tempOperations.get(i);
			if (tempOperation.getArgs().getArg(0).equals(operation.getArgs().getArg(0)) || tempOperation.getArgs().getArg(1).equals(operation.getArgs().getArg(1))) {
				temporaryMappingManager.removeTemporaryMapping(tempOperation);
				i--;
			}
		}
	}

	public KnowledgeBase getSourceOntology() {
		return sourceKb;
	}

	public KnowledgeBase getTargetOntology() {
		return targetKb;
	}

	public KnowledgeBase getMappingOntology() {
		return mappingKb;
	}

	public MappingCollection getMappingCollection() {
		return mappingCollection;
	}

	public TemporaryMappingManager getTemporaryMappingManager() {
		return temporaryMappingManager;
	}

	public SuggestionsManager getSuggestionsManager() {
		return suggestionsManager;
	}

	private KnowledgeBase loadOntology(String projectFile) {
		if (projectFile != null) {
			if(projectFile.endsWith(".pprj")) {
				try {
					Collection<Object> errors = new ArrayList<Object>(0);
					Project project = ProjectManager.getProjectManager().loadProjectFromURI(URIUtilities.createURI(projectFile), errors);
	
					return project.getKnowledgeBase();
				} catch (Exception ex) {
					ex.printStackTrace();
				}
			}
			else if(projectFile.endsWith(".ecore")) {
				return loadEcoreModel(projectFile);
			}
			else if(projectFile.endsWith(".owl")) {
				return loadOWLOntology(projectFile);
			}
		}

		return null;
	}
	
	private KnowledgeBase loadEcoreModel(String ecoreFilePath) {
		try {
			String owlOutputFile = ecoreFilePath.replace(".ecore", ".owl");
			
			Ecore2OWL.ecore2OWL(ecoreFilePath, owlOutputFile, null);
			
			return loadOWLOntology(owlOutputFile);
		} catch (EODMOWLTransformerException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		return null;
	}
	
	@SuppressWarnings("unchecked")
	private KnowledgeBase loadOWLOntology(String owlFilePath) {
		CreateOWLProjectFromFilePlugin creator = new CreateOWLProjectFromFilePlugin();

		File owlFile = new File(owlFilePath);
		
		List errors = new ArrayList();
		Project p = creator.createProject(owlFile, errors);
		p.setProjectURI(new File(owlFilePath.replace(".owl", ".pprj")).toURI());
		JenaKnowledgeBaseFactory.setOWLFileName(p.getSources(), owlFile.toURI().toString());
				
		p.save(errors);
		
		return p.getKnowledgeBase();
	}

	/**
	 * Loads the mapping elements based on the mapping ontology information.
	 */
	private void loadMapping() {
		MappingStoragePlugin mappingStoragePlugin = PluginManager.getInstance().getFirstActiveMappingStoragePlugin();
		Cls mappingInstances = mappingStoragePlugin.getClassForClassToClassMappings();

		for (Instance instance : mappingInstances.getInstances()) {
			Object[] mappedClses = mappingStoragePlugin.getMappedClses(instance);
			if (mappedClses.length == 2) {
				if (mappedClses[0] instanceof Frame && mappedClses[1] instanceof Frame) {
					Frame sourceCls = (Frame) mappedClses[0];
					Frame targetCls = (Frame) mappedClses[1];
					
					String annotation = "<b>" + sourceCls.getBrowserText() + "</b> mapped to <b>" + targetCls.getBrowserText() + "</b>";

					addMappingElements(null, sourceCls, targetCls, mappingCollection, annotation, MappingType.VERIFIED_MAPPING);
				}
			}
		}
	}

	/**
	 * Creates candidate mapping elements for all suggested mappings.
	 */
	private void loadSuggestedMappings() {
		for (Object object : suggestionsManager.getAllSuggestions()) {
			Operation operation = (Operation) object;

			// make sure this operation hasn't been flagged as temporary
			if (!temporaryMappingManager.isTemporaryOperation(operation)) {
				if (operation.getArgs().getArg(0) instanceof Frame && operation.getArgs().getArg(1) instanceof Frame) {
					Frame sourceFrame = (Frame) operation.getArgs().getArg(0);
					Frame targetFrame = (Frame) operation.getArgs().getArg(1);
					
					//if(sourceFrame instanceof Slot) {
					//	System.out.println(Mappings.getRealName(sourceFrame));
					//	sourceFrame = sourceKb.getSlot(Mappings.getRealName(sourceFrame));
					//}
					if(sourceKb.getFrame(sourceFrame.getFrameID()) == null) {
						sourceFrame = Mappings.getSingleSource(sourceFrame);
					}
					//if(targetFrame instanceof Slot) {
					//	targetFrame = targetKb.getSlot(Mappings.getRealName(targetFrame));
					//}
					if(targetKb.getFrame(targetFrame.getFrameID()) == null) { 
						targetFrame = Mappings.getSingleSource(targetFrame);
					}
					
					// ignore mappings within the same ontology
					if(sourceFrame == null || targetFrame == null || sourceFrame.getKnowledgeBase().equals(targetFrame.getKnowledgeBase())) {
						continue;
					}
					
					String annotation = getAnnotation(operation);

					addMappingElements(operation, sourceFrame, targetFrame, mappingCollection, annotation, MappingType.CANDIDATE_MAPPING);
				}
			}
		}
	}

	/**
	 * Builds the candidate mapping annotation for the given operation based on Prompt's explanation
	 * facilities.
	 * 
	 * @param operation
	 * @return
	 */
	private String getAnnotation(Operation operation) {
		StringBuffer annotation = new StringBuffer();
		boolean hasFrameAnnotation = false;

		for (Object o : operation.getReason()) {
			if (o instanceof Explanation) {
				Explanation exp = (Explanation) o;
				annotation.append(exp.getShortName());
				if (exp.getArgs() != null) {
					annotation.append(" ");
					boolean firstArgument = true;
					boolean stringAdded = false;
					for (Object argument : exp.getArgs().toArray()) {
						if (argument instanceof Frame) {
							annotation.append(getFrameAnnotation(exp, (Frame) argument, firstArgument, stringAdded));
							firstArgument = false;
							stringAdded = false;
							hasFrameAnnotation = true;
						} else {
							annotation.append(argument.toString() + " ");
							stringAdded = true;
						}
					}
					annotation.append("<br>");
				}
			}
		}

		if (!hasFrameAnnotation) {
			Frame sourceCls = (Frame) operation.getArgs().getArg(0);
			Frame targetCls = (Frame) operation.getArgs().getArg(1);

			String knowledgeBaseName = ProjectsAndKnowledgeBases.getKnowledgeBasePrettyName(sourceKb);
			annotation.append("<b>" + sourceCls.getBrowserText() + "</b> (<i>" + knowledgeBaseName + "</i>) ");
			annotation.append(" <--> ");
			knowledgeBaseName = ProjectsAndKnowledgeBases.getKnowledgeBasePrettyName(targetKb);
			annotation.append("<b>" + targetCls.getBrowserText() + "</b> (<i>" + knowledgeBaseName + "</i>) ");
		}

		return annotation.toString();
	}

	/**
	 * Constructs the annotation for an Explanation where part of the argument is a Frame object.
	 */
	private String getFrameAnnotation(Explanation exp, Frame frame, boolean firstArgument, boolean stringAdded) {
		StringBuffer annotation = new StringBuffer();
		if (firstArgument && stringAdded && exp.getConnectorString() != null) {
			annotation.append(exp.getConnectorString() + " ");
			firstArgument = false;
		}

		KnowledgeBase source = frame.getKnowledgeBase();
		if (source == ProjectsAndKnowledgeBases.getTargetKnowledgeBase()) {
			frame = (Frame) CollectionUtilities.getFirstItem(Mappings.getSources(frame));
			if (frame != null) {
				source = frame.getKnowledgeBase();
			}
		}

		String knowledgeBaseName = ProjectsAndKnowledgeBases.getKnowledgeBasePrettyName(source);
		annotation.append("<b>" + frame.getBrowserText() + "</b> (<i>" + knowledgeBaseName + "</i>) ");

		if (firstArgument && exp.getConnectorString() != null) {
			annotation.append(exp.getConnectorString() + " ");
		} else if (!firstArgument && exp.getConnectorString2() != null) {
			annotation.append(exp.getConnectorString2() + " ");
		}

		return annotation.toString();
	}

	/**
	 * Creates temporary mapping elements based on the stored temporary operations.
	 */
	private void loadTemporaryMappings() {
		for (Operation operation : temporaryMappingManager.getTemporaryMappings()) {
			Cls sourceCls = (Cls) operation.getArgs().getArg(0);
			Cls targetCls = (Cls) operation.getArgs().getArg(1);
			String operationAnnotation = getAnnotation(operation);
			String annotation = "Temporary mapping";
			if (operationAnnotation.length() > 0) {
				annotation += ":<br>" + getAnnotation(operation);
			}

			addMappingElements(operation, sourceCls, targetCls, mappingCollection, annotation, MappingType.TEMPORARY_MAPPING);
		}
	}

	/**
	 * Creates all the mapping elements for the sourceFrame and targetFrame
	 */
	@SuppressWarnings("unchecked")
	private void addMappingElements(Operation operation, Frame sourceFrame, Frame targetFrame, MappingCollection mappingCollection, String annotation, MappingType mappingType) {
		Collection<Collection> sourcePaths = new ArrayList<Collection>();
		ArrayList<Frame> sourcePathFrame = new ArrayList<Frame>();
		if(sourceFrame instanceof Cls) {
			TreeUtil.buildObjectPaths((Cls)sourceFrame, sourcePathFrame, sourcePaths, false);
		}
		else if(sourceFrame instanceof Slot) {
			sourcePathFrame.add(sourceFrame);
			sourcePaths.add(sourcePathFrame);
		}
	
		Collection<Collection> targetPaths = new ArrayList<Collection>();
		ArrayList<Frame> targetPathFrame = new ArrayList<Frame>();
		if(targetFrame instanceof Cls) {
			TreeUtil.buildObjectPaths((Cls)targetFrame, targetPathFrame, targetPaths, false);
		}
		else if(targetFrame instanceof Slot) {
			targetPathFrame.add(targetFrame);
			targetPaths.add(targetPathFrame);
		}
		
		for (Collection sourcePath : sourcePaths) {
			for (Collection targetPath : targetPaths) {
				mappingCollection.addMapping(MappingUtil.createMappingElement(operation, mappingType, sourcePath, targetPath, annotation));
			}
		}
	}
	/**
	 * Gets the slot value for a given mapping instance and a given slot mapping.
	 */
//	private String getSlotValue(Slot instanceSlot, Slot valueSlot, Instance mapping) {
//		Instance component = (Instance) mapping.getOwnSlotValue(instanceSlot);
//		String value = (String) component.getOwnSlotValue(valueSlot);
//
//		return value;
//	}

	public void setShowSlots(boolean showSlots) {
		this.showSlots = showSlots;
	}

	public boolean isShowSlots() {
		return showSlots;
	}
}
